package com.flyqiu.flow.core.handler;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.flyqiu.common.bean.FlyQiuBeanManage;
import com.flyqiu.common.tools.FlyQiuCollectionTools;
import com.flyqiu.common.user.FlyQiuUser;
import com.flyqiu.flow.api.candidate.FlowInstCandidateContext;
import com.flyqiu.flow.api.candidate.FlyQiyFlowCandidateManage;
import com.flyqiu.flow.api.context.ProcInstContext;
import com.flyqiu.flow.api.script.IVariableScope;
import com.flyqiu.flow.api.service.FlowRuActivityService;
import com.flyqiu.flow.api.service.FlowRuUserTaskService;
import com.flyqiu.flow.bpmn.base.ScriptModel;
import com.flyqiu.flow.bpmn.element.UserTask;

import com.flyqiu.flow.common.enums.IdentityLinkType;
import com.flyqiu.flow.common.enums.ProcInstStatus;
import com.flyqiu.flow.common.enums.ProcTaskDelegateStatus;
import com.flyqiu.flow.common.enums.ProcTaskStatus;
import com.flyqiu.flow.common.std.ActivityHandlerResult;
import com.flyqiu.flow.core.candidate.DefaultFlowInstCandidateContext;
import com.flyqiu.flow.core.tools.EventTools;
import com.flyqiu.flow.core.var.VariableScope;
import com.flyqiu.flow.event.UserTaskEvent;
import com.flyqiu.flow.event.UserTaskEventContext;
import com.flyqiu.flow.event.api.FlyQiuFlowEventManage;
import com.flyqiu.flow.mapper.HiIdentityLinkMapper;
import com.flyqiu.flow.mapper.HiUserTaskInstMapper;
import com.flyqiu.flow.mapper.ProcHiActivityMapper;
import com.flyqiu.flow.mapper.RuIdentityLinkMapper;
import com.flyqiu.flow.model.entity.base.FlowBaseDao;
import com.flyqiu.flow.model.entity.base.Task;
import com.flyqiu.flow.model.entity.hi.ProcHiActivity;
import com.flyqiu.flow.model.entity.hi.ProcHiUserTask;
import com.flyqiu.flow.model.entity.ru.ProcRuActivity;
import com.flyqiu.flow.model.entity.ru.ProcRuIdentityLink;
import com.flyqiu.flow.model.entity.ru.ProcRuInst;
import com.flyqiu.flow.model.entity.ru.ProcRuUserTask;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;


@EqualsAndHashCode(callSuper = true)
@Data
public class UserTaskHandler extends BaseProcActElementHandler<UserTask, ProcRuUserTask> {

    private String procDefKey;
    private String procDefId;
    private LocalDateTime createTime;


    private ProcHiActivityMapper hiActivityMapper;
    private FlowRuUserTaskService userTaskService;
    private HiUserTaskInstMapper hiUserTaskInstMapper;

    private FlowRuActivityService ruActivityService;
    private FlyQiuFlowEventManage procEventManage;

    private FlyQiyFlowCandidateManage procCandidateManage;
    private RuIdentityLinkMapper ruIdentityLinkMapper;
    private HiIdentityLinkMapper hiIdentityLinkMapper;

    private DispatchEvent dispatchEvent;

    @Override
    protected void initBean(FlyQiuBeanManage procBeanManage) {
        dispatchEvent = new DispatchEvent();
        userTaskService = procBeanManage.getBean(FlowRuUserTaskService.class);
        hiUserTaskInstMapper = procBeanManage.getMapper(HiUserTaskInstMapper.class);
        ruIdentityLinkMapper = procBeanManage.getMapper(RuIdentityLinkMapper.class);
        hiIdentityLinkMapper = procBeanManage.getMapper(HiIdentityLinkMapper.class);
        hiActivityMapper = procBeanManage.getMapper(ProcHiActivityMapper.class);
        procEventManage = procContext.getGlobalContext().getFlowEventManage();
        procCandidateManage = procContext.getGlobalContext().getFlowCandidateManage();
    }


    private List<ProcRuIdentityLink> ruIdentityLinks = null;

    @SuppressWarnings("unchecked")
    private void initLoadCandidateUser(ProcRuUserTask userTaskInst) {
        if (ruIdentityLinks != null) {
            return;
        }

        FlowInstCandidateContext context = procCandidateManage.createCandidateContext(new DefaultFlowInstCandidateContext(procDeployDef, procContext, currentElement, userTaskInst));
        Collection<? extends FlyQiuUser> tempCandidateUsers = procCandidateManage.loadInstCandidates(context);
        if (tempCandidateUsers == null || tempCandidateUsers.isEmpty()) {
            ruIdentityLinks = new ArrayList<>();
            return;
        }
        ruIdentityLinks = tempCandidateUsers.stream().map(user -> {
            ProcRuIdentityLink procRuIdentityLink = ProcRuIdentityLink.create(user, userTaskInst.getProcInstId(), userTaskInst.getActId(), userTaskInst.getId(), IdentityLinkType.CANDIDATE.getValue());
            procRuIdentityLink.setCreateTime(LocalDateTime.now());
            procRuIdentityLink.setType(IdentityLinkType.CANDIDATE.getValue());
            return procRuIdentityLink;
        }).collect(Collectors.toList());
    }


    private Integer taskCount = null;

    private Integer loadCount() {
        if (null == currentElement.getMultiInstance()) {
            taskCount = 1;
            return taskCount;
        }
        ScriptModel instanceNumScript = currentElement.getMultiInstance().getInstanceNumScript();
        taskCount = scriptManage.execute(instanceNumScript, procContext.getProcVariables(), Integer.class);
        taskCount = Math.max(taskCount, 1);
        return taskCount;
    }

    private Integer currentTaskIndex = 0;

    private List<ProcRuIdentityLink> getCurrentTaskIdentityLink(ProcRuUserTask userTaskInst) {
        if (currentElement.getMultiInstance() == null) {
            return ruIdentityLinks;
        }
        Integer taskCount = loadCount();
        if (currentTaskIndex >= taskCount) {
            return null;
        }
        if (ruIdentityLinks == null || ruIdentityLinks.isEmpty()) {
            return null;
        }
        int size = ruIdentityLinks.size();
        if (currentTaskIndex >= size) {
            return null;
        }
        ProcRuIdentityLink procRuIdentityLink = ruIdentityLinks.get(currentTaskIndex);
        procRuIdentityLink.setCreateTime(LocalDateTime.now());
        procRuIdentityLink.setActId(userTaskInst.getActId());
        procRuIdentityLink.setActInstId(userTaskInst.getId());
        procRuIdentityLink.setTenantId(userTaskInst.getTenantId());
        procRuIdentityLink.setProcInstId(userTaskInst.getProcInstId());
        ArrayList<ProcRuIdentityLink> result = new ArrayList<>();
        result.add(procRuIdentityLink);
        return result;

    }

    /**
     * @param currentProcInst 当前流程实例
     * @param ruActivity      当前活动信息
     */
    @Override
    protected ActivityHandlerResult<ProcRuUserTask> startElementInst(ProcRuInst currentProcInst, ProcRuActivity ruActivity) {
        UserTask currentElement = getCurrentElement();
        ProcRuUserTask userTaskInst = new ProcRuUserTask();
        userTaskInst.setId(null);


        userTaskInst.setStatus(ProcTaskStatus.ACTIVATE.getValue());
        userTaskInst.setActId(ruActivity.getId());
        userTaskInst.setTaskDefKey(currentElement.getDefKey());
        userTaskInst.setTaskDefName(currentElement.getDefName());
        userTaskInst.setCreateTime(LocalDateTime.now());
        userTaskInst.setProcInstId(currentProcInst.getId());
        userTaskInst.setProcDeployDefId(currentProcInst.getProcDeployDefId());
        userTaskInst.setProcDeployDefKey(currentProcInst.getProcDeployDefKey());
        userTaskInst.setTenantId(currentProcInst.getTenantId());
        userTaskInst.setParentTaskId("");
        userTaskInst.setDelegateStatus(ProcTaskDelegateStatus.NO_DELEGATE.getValue());

        userTaskInst.setStatus(ProcTaskStatus.ACTIVATE.getValue());
        if (currentElement.getMultiInstance() != null && currentElement.getMultiInstance().getIsSequential() && currentTaskIndex > 0) {
            userTaskInst.setStatus(ProcTaskStatus.SUSPEND.getValue());
        }
        userTaskService.save(userTaskInst);
        UserTaskEventContext eventContext = EventTools.createContext(procDeployDef, currentProcInst, currentElement, userTaskInst);
        dispatchEvent.createBefore(eventContext);
        initLoadCandidateUser(userTaskInst);
        List<ProcRuIdentityLink> identityLinks = getCurrentTaskIdentityLink(userTaskInst);
        if (FlyQiuCollectionTools.isNotEmpty(identityLinks)) {

            ProcRuIdentityLink ruIdentityLink = identityLinks.get(0);
            userTaskInst.setAssigneeKey(ruIdentityLink.getUserKey());
            userTaskInst.setAssignee(ruIdentityLink.getUserId());
            userTaskInst.setAssigneeUniqueKey(ruIdentityLink.getUserUniqueKey());
            userTaskInst.setAssigneeNickName(ruIdentityLink.getUserNickName());
            userTaskInst.setAssigneeAttr(ruIdentityLink.getUserAttr());
            Db.updateById(userTaskInst);
            Db.saveBatch(identityLinks);
        }
        ProcHiUserTask hiUserTaskInst = ProcHiUserTask.createByUserTaskInst(userTaskInst, null);
        hiUserTaskInstMapper.insert(hiUserTaskInst);

        dispatchEvent.createAfter(eventContext);

        ActivityHandlerResult<ProcRuUserTask> activityHandlerResult = new ActivityHandlerResult<>(userTaskInst, userTaskInst.getId());

        activityHandlerResult.setAssignee(userTaskInst.getAssignee());
        activityHandlerResult.setAssigneeKey(userTaskInst.getAssigneeKey());
        activityHandlerResult.setAssigneeUniqueKey(userTaskInst.getAssigneeUniqueKey());
        activityHandlerResult.setAssigneeNickName(userTaskInst.getAssigneeNickName());
        activityHandlerResult.setAssigneeAttr(userTaskInst.getAssigneeAttr());


        if (currentElement.getMultiInstance() == null) {
            return activityHandlerResult;
        }
        this.currentTaskIndex = this.currentTaskIndex + 1;
        if (currentTaskIndex >= this.getTaskCount()) {
            return activityHandlerResult;
        }
        start(procContext, currentDefKey);
        return activityHandlerResult;

    }


    @Override
    protected boolean completeElementInst(ProcInstContext procContext, ProcRuActivity ruActivity) {

        ProcRuUserTask userTask = userTaskService.getByActId(ruActivity.getId());
        if (userTask == null) {
            throw new RuntimeException("not find task by actId:" + ruActivity.getId());
        }

        Integer status = userTask.getStatus();
        if (status == null) {
            status = ProcTaskStatus.ACTIVATE.getValue();
        }
        if (!status.equals(ProcTaskStatus.ACTIVATE.getValue())) {
            throw new RuntimeException("current task not active by taskInstId:" + userTask.getId());
        }
        ProcRuInst procInst = procContext.getProcInst();
        if (procInst.getStatus() == null) {
            procInst.setStatus(ProcTaskStatus.ACTIVATE.getValue());
        }
        if (!procInst.getStatus().equals(ProcInstStatus.ACTIVATE.getValue())) {
            throw new RuntimeException("current procInst not active by procInstId:" + procInst.getId());
        }
        ProcHiUserTask hiUserTaskInst = hiUserTaskInstMapper.selectById(userTask.getId());
        hiUserTaskInst.completeEndTime(LocalDateTime.now());
        hiUserTaskInst.completeAssignee(userTask.getAssignee(), userTask.getAssigneeKey());
        hiUserTaskInst.completeOwner(userTask.getOwner(), userTask.getOwnerKey());
        UserTaskEventContext eventContext = EventTools.createContext(procDeployDef, procInst, currentElement, hiUserTaskInst);
        dispatchEvent.completeBefore(eventContext);
        hiUserTaskInstMapper.updateById(hiUserTaskInst);
        userTaskService.removeById(userTask.getId());
        dispatchEvent.completeAfter(eventContext);

        return true;
    }


    /**
     * 启动下一节点 前检测当前任务是否是多实例 如果不是多实例启动下一环节，是多实例处理多实例逻辑
     *
     * @param procContext       流程上下文
     * @param currentRuActivity 当前活动
     */
    @Override
    protected void startNextActivity(ProcInstContext procContext, ProcRuActivity currentRuActivity) {
        if (currentElement.getMultiInstance() == null) {//如果不是多任务实例 直接启动下一个活动
            super.startNextActivity(procContext, currentRuActivity);
            return;
        }
        ScriptModel completeConditionScript = currentElement.getMultiInstance().getCompleteConditionScript();
        if (completeConditionScript == null) {
            throw new RuntimeException("completeConditionScript cannot be null ");
        }
        LambdaQueryWrapper<ProcRuActivity> curRunActQuery = createCurRunActQuery(currentRuActivity);
        Long count = ruActivityMapper.selectCount(curRunActQuery);
        count = count == null ? 0L : count;
        if (count < 1) {
            super.startNextActivity(procContext, currentRuActivity);
            return;
        }
        VariableScope multiInstanceVariable = createMultiInstanceVariable(currentRuActivity);
        IVariableScope procVariables = procContext.getProcVariables();
        procVariables.append(multiInstanceVariable);
        boolean executed = scriptManage.executeBooleanFullTrue(completeConditionScript, procVariables);
        procVariables.remove(procVariables);
        if (!executed) {
            return;
        }
        //清理不需要的活动以及任务数据
        clearMultiInstanceInfo(currentRuActivity);
        super.startNextActivity(procContext, currentRuActivity);
    }

    private LambdaQueryWrapper<ProcRuActivity> createCurRunActQuery(ProcRuActivity currentRuActivity) {
        LambdaQueryWrapper<ProcRuActivity> ruActQuery = new LambdaQueryWrapper<>();
        ruActQuery.select(FlowBaseDao::getId);
        ruActQuery.eq(ProcRuActivity::getProcInstId, currentRuActivity.getProcInstId());
        ruActQuery.eq(ProcRuActivity::getDefKey, currentRuActivity.getDefKey());
        ruActQuery.eq(ProcRuActivity::getTenantId, currentRuActivity.getTenantId());
        return ruActQuery;
    }

    private void clearMultiInstanceInfo(ProcRuActivity currentRuActivity) {
        LambdaQueryWrapper<ProcRuActivity> ruActQuery = createCurRunActQuery(currentRuActivity);

        Set<String> actIds = ruActivityMapper.selectList(ruActQuery).stream().map(FlowBaseDao::getId).collect(Collectors.toSet());
        ruActivityMapper.delete(ruActQuery);
        if (!actIds.isEmpty()) {
            LambdaQueryWrapper<ProcRuIdentityLink> ruIdentQuery = new LambdaQueryWrapper<>();
            ruIdentQuery.eq(ProcRuIdentityLink::getProcInstId, currentRuActivity.getProcInstId());
            ruIdentQuery.eq(ProcRuIdentityLink::getTenantId, currentRuActivity.getTenantId());
            ruIdentQuery.in(ProcRuIdentityLink::getActId, actIds);
            ruIdentityLinkMapper.delete(ruIdentQuery);
        }

        LambdaQueryWrapper<ProcRuUserTask> ruTaskQuery = new LambdaQueryWrapper<>();
        ruTaskQuery.eq(Task::getProcInstId, currentRuActivity.getProcInstId());
        ruTaskQuery.eq(Task::getTaskDefKey, currentRuActivity.getDefKey());
        ruTaskQuery.eq(Task::getTenantId, currentRuActivity.getTenantId());
        userTaskService.remove(ruTaskQuery);

        LambdaQueryWrapper<ProcHiActivity> hiActQuery = new LambdaQueryWrapper<>();
        hiActQuery.eq(ProcHiActivity::getProcInstId, currentRuActivity.getProcInstId());
        hiActQuery.eq(ProcHiActivity::getDefKey, currentRuActivity.getDefKey());
        hiActQuery.eq(ProcHiActivity::getTenantId, currentRuActivity.getTenantId());
        hiActQuery.isNull(ProcHiActivity::getEndTime);
        hiActivityMapper.delete(hiActQuery);

        LambdaQueryWrapper<ProcHiUserTask> hiTaskQuery = new LambdaQueryWrapper<>();
        hiTaskQuery.select(FlowBaseDao::getId, ProcHiUserTask::getEndTime);
        hiTaskQuery.eq(Task::getProcInstId, currentRuActivity.getProcInstId());
        hiTaskQuery.eq(Task::getTaskDefKey, currentRuActivity.getDefKey());
        hiTaskQuery.eq(Task::getTenantId, currentRuActivity.getTenantId());
        hiTaskQuery.isNull(ProcHiUserTask::getEndTime);
        hiUserTaskInstMapper.delete(hiTaskQuery);
    }

    /**
     * 创建任务多实例所需流程变量
     *
     * @param userTask 当前运行的流程任务
     * @return VariableScope
     */
    private VariableScope createMultiInstanceVariable(ProcRuActivity userTask) {
        LambdaQueryWrapper<ProcRuUserTask> ruQueryWrapper = new LambdaQueryWrapper<>();
        ruQueryWrapper.eq(Task::getProcInstId, userTask.getProcInstId());
        ruQueryWrapper.eq(Task::getTaskDefKey, userTask.getDefKey());
        ruQueryWrapper.eq(ProcRuUserTask::getStatus, ProcTaskStatus.ACTIVATE.getValue());
        ruQueryWrapper.eq(Task::getTenantId, userTask.getTenantId());

        long taskOfActiveCount = userTaskService.count(ruQueryWrapper);
        LambdaQueryWrapper<ProcHiUserTask> hiQueryWrapper = new LambdaQueryWrapper<>();
        hiQueryWrapper.select(FlowBaseDao::getId, ProcHiUserTask::getEndTime);
        hiQueryWrapper.eq(Task::getProcInstId, userTask.getProcInstId());
        hiQueryWrapper.eq(Task::getTaskDefKey, userTask.getDefKey());
        hiQueryWrapper.eq(Task::getTenantId, userTask.getTenantId());
        List<ProcHiUserTask> procHiUserTasks = hiUserTaskInstMapper.selectList(hiQueryWrapper);
        long taskOfCount = procHiUserTasks.size();
        long taskOfCompleteCount = procHiUserTasks.stream().filter(e -> null != e.getEndTime()).count();
        VariableScope variableScope = new VariableScope(procContext.getGlobalContext().getFlowVarSerializeManage());
        variableScope.addTempVar("taskOfActiveCount", taskOfActiveCount);
        variableScope.addTempVar("taskOfCompleteCount", taskOfCompleteCount);
        variableScope.addTempVar("taskOfCount", taskOfCount);
        return variableScope;
    }

    private class DispatchEvent implements UserTaskEvent {
        @Override
        public void createBefore(UserTaskEventContext context) {
            procEventManage.triggerCreateBeforeEvent(UserTaskEvent.class, context);
        }

        @Override
        public void createAfter(UserTaskEventContext context) {
            procEventManage.triggerCreateAfterEvent(UserTaskEvent.class, context);
        }

        @Override
        public void completeBefore(UserTaskEventContext context) {
            procEventManage.triggerCompleteBeforeEvent(UserTaskEvent.class, context);
        }

        @Override
        public void completeAfter(UserTaskEventContext context) {
            procEventManage.triggerCompleteAfterEvent(UserTaskEvent.class, context);
        }

        @Override
        public void deleteBefore(UserTaskEventContext context, JSONObject params, String reason) {
            procEventManage.triggerDeleteBeforeEvent(UserTaskEvent.class, context, params, reason);
        }

        @Override
        public void deleteAfter(UserTaskEventContext context, JSONObject params, String reason) {
            procEventManage.triggerDeleteAfterEvent(UserTaskEvent.class, context, params, reason);
        }
    }

}
